/*
 * Copyright 2010-2013, David George, Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.magneato.dao;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.Iterator;

import javax.servlet.ServletContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.magneato.dto.Page;
import org.magneato.service.FileUploader;
import org.magneato.utils.parsers.WikiParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;

/**
 * Needs to set RootURL and Destination Directory
 * 
 * Be nice to apply aspects for max file size and resize if image
 * 
 * @author David George
 * 
 */
@Service
public class FileSystemUploader implements FileUploader, ServletContextAware {
	@Value("${mediaRoot}")
	private String mediaDir;

	ServletContext servletContext;

	private final Log _logger = LogFactory.getLog(FileSystemUploader.class);

	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;

		File destDir = new File(servletContext.getRealPath(mediaDir));
		if (!destDir.exists()) {
			destDir.mkdirs();
		}
	}

	/**
	 * Copies file to destination directory on local file system, this must be a
	 * directory accessible by a web server. Returns a URL pointing to the file.
	 * <p>
	 * Note the destination file path name is composed of a 3 letter hash of the
	 * UUID Name followed by the the Page NameSpace. The Page's UUID is used as
	 * a prefix. This distributes files over 2 top level directories each
	 * containing up to 1000 sub-directories. The destination filename is unique
	 * for a given page as it is coupled with the page's UUID. This enables us
	 * to locate all current attachments for a page and also find orphans. We
	 * can do this when saving.
	 * 
	 * @param srcFile
	 *            - Temporary location on local filesystem of source file.
	 * @param destFileName
	 *            - Destination filename as uploaded via form
	 * @param page
	 *            - Page we are attaching file to
	 * 
	 * @return
	 */
	public String upload(File srcFile, String destFileName, Page page) {
		String rootPath = getRootPath(page);

		// strip extension
		String ext = "";
		int dotIndex = destFileName.lastIndexOf('.');
		if (dotIndex > 0) {
			ext = destFileName.substring(dotIndex);
			destFileName = destFileName.substring(0, dotIndex);
		}
		destFileName = WikiParser.cleanURLText(destFileName) + ext;
		destFileName = rootPath + "/" + page.getUuid() + "_"
				+ destFileName.toLowerCase();

		File destFile = new File(servletContext.getRealPath(mediaDir
				+ destFileName));
		destFile.getParentFile().mkdirs();

		try {
			copyFile(srcFile, destFile);
		} catch (IOException e) {
			// can't do much about this and can't give the end user an error so
			// keep calm and carry on, we'll return the destination file as if
			// nothing had happened
			_logger.error("Can't upload attachement " + destFile + " "
					+ e.getLocalizedMessage());
		}

		return "/" + mediaDir + destFileName;
	}

	/**
	 * @param page
	 *            - Page object
	 * 
	 * @return
	 */
	public Iterator<File> findAttachments(Page page) throws IOException {
		final String prefix = page.getUuid() + "_";

		String rootPath = getRootPath(page);
		File attachDir = new File(servletContext.getRealPath(mediaDir
				+ rootPath));
		System.out.println("delete dir path.1 " + attachDir.getAbsolutePath());
		// Exception here
		return FileUtils.iterateFiles(attachDir, new PrefixFileFilter(prefix),
				null);
	}

	/**
	 * Moves all attachments associated with a page to a "hidden" deleted
	 * directory
	 * 
	 * @param
	 * @returns
	 * @throws
	 */
	public int deleteAttachments(Page page) throws IOException {
		int count = 0;
		String rootPath = getRootPath(page);
		System.out.println("delete dir " + servletContext.getRealPath(mediaDir
				+ rootPath + "/.deleted"));
		File deleteDir = new File(servletContext.getRealPath(mediaDir
				+ rootPath + "/.deleted"));

		Iterator<File> i = findAttachments(page);

		while (i.hasNext()) {
			File file = i.next();
			_logger.info("Deleting attachment " + file.getName()
					+ " for document " + page.getName());
			FileUtils.moveFileToDirectory(file, deleteDir, true);
		}
		return count;
	}

	/**
	 * For performance reasons we want to split uploads over a hierarchy of
	 * directories with random names. However we base these on something
	 * inviolate so we can find the attachments that belong to a page later.
	 * 
	 * @param page
	 * @return
	 */
	private final String getRootPath(Page page) {
		String level1 = page.getUuid();
		String level2 = level1.substring(level1.length()-3);
		level1 = level1.substring(0, 3);
		
		return level1 + "/" + level2;
	}

	private void copyFile(File in, File out) throws IOException {
		FileChannel inChannel = new FileInputStream(in).getChannel();
		// if remote dir does not exist need to make it
		FileChannel outChannel = new FileOutputStream(out).getChannel();
		try {
			inChannel.transferTo(0, inChannel.size(), outChannel);
		} catch (IOException e) {
			throw e;
		} finally {
			if (inChannel != null)
				inChannel.close();
			if (outChannel != null)
				outChannel.close();
		}
	}
}
